function init() {
    myDiagram = new go.Diagram('myDiagramDiv', {
        'undoManager.isEnabled': true, // enable undo & redo
        'themeManager.changesDivBackground': true,
        'themeManager.currentTheme': "white",
        'grid.visible': true,
        'grid.gridCellSize': new go.Size(10, 10),
        // 'grid.snapToGrid': true,
        'layout': new go.LayeredDigraphLayout({
            direction: 90,
            layerSpacing: 60,
            columnSpacing: 40,
            setsPortSpots: true
        }),
        'commandHandler.zoomFactor': 1.2,
        'toolManager.mouseWheelBehavior': go.ToolManager.WheelZoom
    });

    // Add zoom controls to toolbar
    const toolbar = document.querySelector('.toolbar');
    toolbar.innerHTML += `
        <button class="btn btn-outline-secondary btn-sm me-2" onclick="zoomIn()">放大</button>
        <button class="btn btn-outline-secondary btn-sm me-2" onclick="zoomOut()">缩小</button>
        <button class="btn btn-outline-secondary btn-sm me-2" onclick="zoomToFit()">适应窗口</button>
        <button class="btn btn-outline-secondary btn-sm me-2" onclick="executeFlow()">执行</button>
      `;

    // 加载事件，更新事件
    myDiagram.addDiagramListener('Modified', (e) => {
        console.log("Modified=====")
        const button = document.getElementById('SaveButton');
        if (button) button.disabled = !myDiagram.isModified;
        const idx = document.title.indexOf('*');
        if (myDiagram.isModified) {
            if (idx < 0) document.title += '*';
        } else {
            if (idx >= 0) document.title = document.title.slice(0, idx);
        }
    });

    // 添加鼠标点击事件监听器
    myDiagram.addDiagramListener("ObjectSingleClicked", function (e) {
        const part = e.subject.part;
        if (part instanceof go.Node) {
            console.log(part)
            updatePropertiesPanel(part.data);
            document.getElementById("entityModalBtn").click();
            console.log("Clicked on node: " + part.data.key);
        } else if (part instanceof go.Link) {
            console.log("Clicked on link");
        } else {
            console.log("Clicked on the diagram background");
        }
    });
    // set up some colors/fonts for the default ('light') and dark Themes
    myDiagram.themeManager.set('light', {
        colors: {
            text: '#fff',
            start: '#064e3b',
            step: '#49939e',
            conditional: '#6a9a8a',
            end: '#7f1d1d',
            comment: '#a691cc',
            bgText: '#000',
            link: '#dcb263',
            linkOver: '#cbd5e1',
            div: '#ede9e0'
        }
    });
    myDiagram.themeManager.set('dark', {
        colors: {
            text: '#fff',
            step: '#414a8d',
            conditional: '#88afa2',
            comment: '#bfb674',
            bgText: '#fff',
            link: '#fdb71c',
            linkOver: '#475569',
            div: '#141e37'
        }
    });

    defineFigures();

    // helper definitions for node templates
    function nodeStyle(node) {
        node
            // the Node.location is at the center of each node
            .set({locationSpot: go.Spot.Center})
            // The Node.location comes from the "loc" property of the node data,
            // converted by the Point.parse static method.
            // If the Node.location is changed, it updates the "loc" property of the node data,
            // converting back using the Point.stringify static method.
            .bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify);
    }

    function shapeStyle(shape) {
        // make the whole node shape a port
        shape.set({strokeWidth: 0, portId: '', cursor: 'pointer'});
    }

    function textStyle(textblock) {
        textblock.set({font: 'bold 11pt Figtree, sans-serif'}).theme('stroke', 'text');
    }

    // define the Node templates for regular nodes
    // 定义节点状态颜色
    const statusColors = {
        idle: '#dbeafe',
        running: '#ff0000',  // 改为红色
        success: '#86efac',
        failed: '#fca5a5'
    };

    // 节点点击处理
    function handleNodeClick(node) {
        const nodeId = node.data.key;
        // 调用后台API
        fetch(`/api/run_node/${nodeId}`, {
            method: 'POST'
        })
            .then(response => response.json())
            .then(data => {
                // 更新节点状态
                myDiagram.model.commit(m => {
                    const nodeData = m.findNodeDataForKey(nodeId);
                    if (nodeData) {
                        m.set(nodeData, 'status', data.status);
                    }
                });
                // 启动状态轮询
                startStatusPolling(nodeId);
            });
    }

    // 状态轮询
    function startStatusPolling(nodeId) {
        const interval = setInterval(() => {
            fetch(`/api/get_status/${nodeId}`)
                .then(response => response.json())
                .then(data => {
                    myDiagram.model.commit(m => {
                        const nodeData = m.findNodeDataForKey(nodeId);
                        if (nodeData) {
                            const oldStatus = nodeData.status;
                            m.set(nodeData, 'status', data.status);
                            // 如果状态变为最终状态，停止轮询
                            if (data.status === 'success' || data.status === 'failed') {
                                clearInterval(interval);
                            }
                        }
                    });
                });
        }, 1000); // 每秒轮询一次
    }

    myDiagram.nodeTemplateMap.add(
        '', // the default category
        new go.Node('Auto').apply(nodeStyle)
            .add(
                new go.Shape('Rectangle', {
                    portId: 'main',
                    fromLinkable: true,
                    toLinkable: true,
                    fromSpot: go.Spot.AllSides,
                    toSpot: go.Spot.AllSides,
                    strokeWidth: 2,
                    stroke: '#2563eb',
                    fill: '#dbeafe', // 默认颜色
                    corner: 6
                }).bind('fill', 'status', status => statusColors[status] || '#dbeafe'),
                new go.Panel('Vertical', {margin: 8})
                    .add(
                        new go.Picture({
                            source: '',
                            width: 40,
                            height: 40,
                            margin: new go.Margin(0, 0, 4, 0)
                        }),
                        new go.TextBlock({
                            maxSize: new go.Size(160, NaN),
                            wrap: go.Wrap.Fit,
                            editable: true,
                            font: 'bold 12px Figtree, sans-serif',
                            stroke: '#1e3a8a'
                        }).bindTwoWay('text')
                    )
            )
    );

    //双击执行的方法
    function handlerDC(e, obj) {
        let node = obj.part;//拿到节点的json对象，后面要拿什么值就直接.出来
        let procTaskId = node.data.key;
        let procTaskName = node.data.text;
        let description = node.data.description;
        let procTmplId = node.data.tmplId;
        alert("addNode")
    }

    myDiagram.nodeTemplateMap.add(
        'Conditional',
        new go.Node('Auto').apply(nodeStyle)
            .add(
                new go.Shape('Diamond', {
                    portId: 'main',
                    fromLinkable: true,
                    toLinkable: true,
                    fromSpot: go.Spot.AllSides,
                    toSpot: go.Spot.AllSides,
                    strokeWidth: 2,
                    stroke: '#d97706',
                    fill: '#fef3c7', // 默认颜色
                    minSize: new go.Size(10, 10) // Minimum size
                }).bind('fill', 'status', status => statusColors[status] || '#fef3c7'),
                new go.Panel('Vertical', {margin: 3})
                    .add(
                        new go.Picture({
                            source: '',
                            width: 20,
                            height: 20,
                            margin: new go.Margin(0, 0, 4, 0)
                        }),
                        new go.TextBlock({
                            maxSize: new go.Size(200, NaN),
                            wrap: go.Wrap.Fit,
                            editable: true,
                            font: 'bold 18px Figtree, sans-serif',
                            stroke: '#92400e',
                            textAlign: 'center',
                            margin: 2,
                        })
                            .bindTwoWay('text')
                    )
            )
    );

    myDiagram.nodeTemplateMap.add(
        'Start',
        new go.Node('Auto')
            .apply(nodeStyle)
            .add(
                new go.Shape('Circle', {
                    portId: 'output',
                    fromSpot: go.Spot.Right,
                    fromLinkable: true,
                    toLinkable: true,
                    strokeWidth: 2,
                    stroke: '#059669',
                    fill: '#d1fae5',
                    width: 60,
                    height: 60
                }),
                new go.Panel('Vertical', {margin: 8})
                    .add(
                        new go.Picture({
                            source: '',
                            width: 24,
                            height: 24,
                            margin: new go.Margin(0, 0, 4, 0)
                        }),
                        new go.TextBlock({
                            text: '开始',
                            maxSize: new go.Size(160, NaN),
                            wrap: go.Wrap.Fit,
                            editable: true,
                            font: 'bold 12px Figtree, sans-serif',
                            stroke: '#065f46',
                            textAlign: 'center'
                        })
                    )
            )
    );

    myDiagram.nodeTemplateMap.add(
        'End',
        new go.Node('Auto')
            .apply(nodeStyle)
            .add(
                new go.Shape('Circle', {
                    portId: 'input',
                    toSpot: go.Spot.Top,
                    strokeWidth: 2,
                    stroke: '#dc2626',
                    fill: '#fee2e2',
                    width: 60,
                    height: 60
                }),
                new go.Panel('Vertical', {margin: 8})
                    .add(
                        new go.Picture({
                            source: '',
                            width: 24,
                            height: 24,
                            margin: new go.Margin(0, 0, 4, 0)
                        }),
                        new go.TextBlock({
                            text: '结束',
                            maxSize: new go.Size(160, NaN),
                            wrap: go.Wrap.Fit,
                            editable: true,
                            font: 'bold 12px Figtree, sans-serif',
                            stroke: '#991b1b',
                            textAlign: 'center'
                        })
                    )
            )
    );

    myDiagram.nodeTemplateMap.add(
        'Comment',
        new go.Node('Auto').apply(nodeStyle)
            .add(
                new go.Shape('File', {strokeWidth: 3}).theme('fill', 'div').theme('stroke', 'comment'),
                new go.TextBlock({
                    font: '9pt Figtree, sans-serif',
                    margin: 8,
                    maxSize: new go.Size(200, NaN),
                    wrap: go.Wrap.Fit,
                    textAlign: 'center',
                    editable: true
                })
                    .theme('stroke', 'bgText')
                    .bindTwoWay('text')
                // no ports, because no links are allowed to connect with a comment
            )
    );

    // LLM Node Template
    myDiagram.nodeTemplateMap.add(
        'LLM',
        new go.Node('Auto').apply(nodeStyle)
            .add(
                new go.Shape('RoundedRectangle', {
                    portId: 'main',
                    fromLinkable: true,
                    toLinkable: true,
                    fromSpot: go.Spot.Right,
                    toSpot: go.Spot.Left,
                    strokeWidth: 2,
                    stroke: '#9333ea',
                    fill: '#f3e8ff',
                    corner: 12,
                    shadowBlur: 8,
                    shadowColor: '#9333ea',
                    shadowOffset: new go.Point(2, 2)
                }),
                new go.Picture({
                    source: '',
                    width: 50,
                    height: 50,
                    margin: new go.Margin(0, 0, 4, 0)
                }),
                new go.TextBlock({
                    maxSize: new go.Size(160, NaN),
                    wrap: go.Wrap.Fit,
                    editable: true,
                    font: 'bold 12px Figtree, sans-serif',
                    stroke: '#6b21a8'
                })
                    .bindTwoWay('text')
            )
    );

    // replace the default Link template in the linkTemplateMap
    myDiagram.linkTemplate = new go.Link({
        routing: go.Routing.Orthogonal,
        curve: go.Curve.JumpOver,
        corner: 10,
        toShortLength: 8,
        relinkableFrom: true,
        relinkableTo: true,
        reshapable: true,
        resegmentable: true,
        selectionAdorned: false,
        selectionStrokeWidth: 3,
        shadowVisible: true,
        shadowBlur: 5,
        shadowOffset: new go.Point(2, 2),
        shadowColor: 'rgba(0,0,0,0.2)',
        // Explicit port configuration
        fromPortId: 'output',
        toPortId: 'input',
        fromSpot: go.Spot.Right,
        toSpot: go.Spot.Left,
        // mouse-overs subtly highlight links:
        mouseEnter: (e, link) => (link.findObject('HIGHLIGHT').stroke = link.diagram.themeManager.findValue('linkOver', 'colors')),
        mouseLeave: (e, link) => (link.findObject('HIGHLIGHT').stroke = 'transparent'),
        // context-click creates an editable link label
        contextClick: (e, link) => {
            e.diagram.model.commit((m) => {
                m.set(link.data, 'text', 'Label');
            });
        }
    })
        .bindTwoWay('points')
        .add(
            // the highlight shape, normally transparent
            new go.Shape({
                isPanelMain: true,
                strokeWidth: 8,
                stroke: 'transparent',
                name: 'HIGHLIGHT'
            }),
            // the link path shape
            new go.Shape({isPanelMain: true, strokeWidth: 3})
                .theme('stroke', 'link'),
            // the arrowhead
            new go.Shape({toArrow: 'Triangle', strokeWidth: 0, scale: 2})
                .theme('fill', 'link'),
            // the link label
            new go.Panel('Auto', {visible: false})
                .bind('visible', 'text', (t) => typeof t === 'string' && t.length > 0) // only shown if there is text
                .add(
                    // a gradient that fades into the background
                    new go.Shape('Ellipse', {strokeWidth: 0})
                        .theme('fill', 'div', null, null, c => new go.Brush("Radial", {0: c, 0.5: `${c}00`})),
                    new go.TextBlock({
                        name: 'LABEL',
                        font: '9pt Figtree, sans-serif',
                        margin: 3,
                        editable: true
                    })
                        .theme('stroke', 'bgText')
                        .bindTwoWay('text')
                )
        );

    // temporary links used by LinkingTool and RelinkingTool are also orthogonal:
    myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Routing.Orthogonal;
    myDiagram.toolManager.relinkingTool.temporaryLink.routing = go.Routing.Orthogonal;

    load(); // load an initial diagram from some JSON text

    // initialize the Palette that is on the left side of the page
    myPalette = new go.Palette('myPaletteDiv', {
        nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
        themeManager: myDiagram.themeManager, // share the ThemeManager used by myDiagram
        model: new go.GraphLinksModel([
            // specify the contents of the Palette
            {category: 'Start'},
            {category: 'Step', text: 'Step'},
            {category: 'LLM', text: 'LLM', key: 'llm', loc: '0 0'},
            {category: 'Conditional'},
            {category: 'End'}
        ]),
        layout: new go.GridLayout({
            wrappingColumn: 1,
            alignment: go.GridLayout.Position,
            cellSize: new go.Size(1, 1)
        })
    });
} // end init

// define some custom shapes for node templates
function defineFigures() {
    go.Shape.defineFigureGenerator('Conditional', (shape, w, h) => {
        const geo = new go.Geometry();
        const fig = new go.PathFigure(w * 0.15, 0, true);
        geo.add(fig);
        fig.add(new go.PathSegment(go.SegmentType.Line, w * 0.85, 0));
        fig.add(new go.PathSegment(go.SegmentType.Line, w, 0.5 * h));
        fig.add(new go.PathSegment(go.SegmentType.Line, w * 0.85, h));
        fig.add(new go.PathSegment(go.SegmentType.Line, w * 0.15, h));
        fig.add(new go.PathSegment(go.SegmentType.Line, 0, 0.5 * h).close());
        geo.spot1 = new go.Spot(0.15, 0);
        geo.spot2 = new go.Spot(0.85, 1);
        return geo;
    });

    // taken from ../extensions/Figures.js:
    go.Shape.defineFigureGenerator('File', (shape, w, h) => {
        const geo = new go.Geometry();
        const fig = new go.PathFigure(0, 0, true); // starting point
        geo.add(fig);
        fig.add(new go.PathSegment(go.SegmentType.Line, 0.75 * w, 0));
        fig.add(new go.PathSegment(go.SegmentType.Line, w, 0.25 * h));
        fig.add(new go.PathSegment(go.SegmentType.Line, w, h));
        fig.add(new go.PathSegment(go.SegmentType.Line, 0, h).close());
        const fig2 = new go.PathFigure(0.75 * w, 0, false);
        geo.add(fig2);
        // The Fold
        fig2.add(new go.PathSegment(go.SegmentType.Line, 0.75 * w, 0.25 * h));
        fig2.add(new go.PathSegment(go.SegmentType.Line, w, 0.25 * h));
        geo.spot1 = new go.Spot(0, 0.25);
        geo.spot2 = go.Spot.BottomRight;
        return geo;
    });
}

// Show the diagram's model in JSON format that the user may edit
function save() {
    var jsonString = document.getElementById('mySavedModel').value = myDiagram.model.toJson();
    const jsonObject = JSON.parse(jsonString);
    var nodeDatas = jsonObject.nodeDataArray;
    var linkDatas = jsonObject.linkDataArray;
    var data = {
        "node_data": nodeDatas,
        "link_data": linkDatas,
        "project_id": project_id
    };
    commonPostXhr("/flowchart/nodes_links", data);
    console.log(data)
    myDiagram.isModified = false;
}

function load() {
    fetch(`/flowchart/data/${project_id}`)
        .then(response => response.json())
        .then(data => {
            console.log(data.node_data)
            console.log(data.link_data)
            myDiagram.model = new go.GraphLinksModel(
                {
                    linkFromPortIdProperty: "fromPort",  // required information:
                    linkToPortIdProperty: "toPort",
                    copiesArrays: true,
                    copiesArrayObjects: true,
                    nodeDataArray: data.node_data,
                    linkDataArray: data.link_data
                });
        })

    //  nodeData = [
    //     {"key": -1, "category": "Start", "loc": "-114.6640625 -55.859375", "text": "Start"},
    //     {"category": "Step", "text": "load_data", "key": -8, "loc": "-113.78125 35.76953125"},
    //     {"category": "Step", "text": "preprocess", "key": -9, "loc": "-114.1484375 138"},
    //     {
    //         "category": "Step",
    //         "text": "generate_factor",
    //         "key": -19,
    //         "loc": "-113.65931701660162 246.56851599937264"
    //     },
    //     {"category": "Step", "text": "backtest", "key": -20, "loc": "-285.5861919581911 420.60543050822275"},
    //     {"category": "End", "text": "End", "key": -13, "loc": "-93.10847743273929 682.3625880053542"},
    //     {"category": "Conditional", "text": "evaluate", "key": -10, "loc": "-91.56958548228704 523.8707818169996"}
    // ];
    // var linkData = [
    //     {"from": -1, "to": -8, "text": ""},
    //     {"from": -8, "to": -9, "text": ""},
    //     {"from": -9, "to": -19, "text": ""},
    //     {"from": -19, "to": -20, "text": ""},
    //     {"from": -10, "to": -13, "text": "Yes"},
    //     {"from": -10, "to": -19, "text": "NO"},
    //     {"from": -20, "to": -10, "text": ""}
    // ];
    // myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);

}

// Zoom controls
function zoomIn() {
    myDiagram.commandHandler.increaseZoom();
}

function zoomOut() {
    myDiagram.commandHandler.decreaseZoom();
}

function zoomToFit() {
    myDiagram.commandHandler.zoomToFit();
}

// Update properties panel
function updatePropertiesPanel(data) {
    const panel = document.getElementById('nodeDetailsId');
    console.log(data)
    panel.innerHTML = `
        <div class="mb-3">
          <label class="form-label">节点名称</label>
          <input type="text" disabled class="form-control" value="${data.text || ''}" onchange="updateNodeText(${data.key}, this.value)">
        </div>
        <div class="mb-3">
          <label class="form-label">节点类型</label>
          <input type="text" disabled class="form-control" value="${data.category || ''}" onchange="updateNodeText(${data.key}, this.value)">
        </div>
        <div class="mb-3">
          <label class="form-label">输入</label>
          <textarea id="nodeInput" style="width: 100%; height: 375px"></textarea>
        </div>
      `;
}

// 处理提交按钮点击事件
function submitComment(key) {
    const comment = document.getElementById('nodeComment').value;
    myDiagram.model.commit((m) => {
        const nodeData = m.findNodeDataForKey(key);
        if (nodeData) {
            m.set(nodeData, 'comment', comment);
            alert('备注已保存');
        }
    });
}

function updateNodeText(key, text) {
    myDiagram.model.commit((m) => {
        const nodeData = m.findNodeDataForKey(key);
        if (nodeData) {
            m.set(nodeData, 'text', text);
        }
    });
}

function updateNodeColor(key, color) {
    myDiagram.model.commit((m) => {
        const nodeData = m.findNodeDataForKey(key);
        if (nodeData) {
            m.set(nodeData, 'color', color);
            const node = myDiagram.findNodeForKey(key);
            if (node) {
                node.findObject('SHAPE').fill = color;
            }
        }
    });
}

// print the diagram by opening a new window holding SVG images of the diagram contents for each page
function exportAsImage(format) {
    const diagram = myDiagram;
    const scale = 2; // Higher resolution
    const bounds = diagram.documentBounds;

    if (format === 'png') {
        const img = diagram.makeImage({
            scale: scale,
            size: bounds.size,
            background: '#ffffff'
        });

        const link = document.createElement('a');
        link.download = 'diagram.png';
        link.href = img.src;
        link.click();
    } else if (format === 'svg') {
        const svg = diagram.makeSvg({
            scale: scale,
            size: bounds.size,
            background: '#ffffff'
        });

        const serializer = new XMLSerializer();
        const source = serializer.serializeToString(svg);

        const link = document.createElement('a');
        link.download = 'diagram.svg';
        link.href = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
        link.click();
    }
}

function changeTheme() {
    const myDiagram = go.Diagram.fromDiv('myDiagramDiv');
    if (myDiagram) {
        myDiagram.themeManager.currentTheme = document.getElementById('theme').value;
    }
}

window.addEventListener('DOMContentLoaded', () => {
    // setTimeout only to ensure font is loaded before loading diagram
    // you may want to use an asset loading library for this
    // to keep this sample simple, it does not
    setTimeout(() => {
        init();
    }, 300);
});

// 执行流程图
async function executeFlow() {
    const nodes = myDiagram.model.nodeDataArray;
    const startNode = nodes.find(n => n.category === 'Start');
    if (!startNode) {
        alert('请添加开始节点');
        return;
    }

    // 重置所有节点状态
    myDiagram.model.commit(m => {
        nodes.forEach(n => m.set(n, 'status', 'idle'));
    });

    // 从开始节点执行
    await executeNode(startNode);

    async function executeNode(node) {
        // 设置当前节点为运行中
        myDiagram.model.commit(m => {
            m.set(node, 'status', 'running');
        });
        // 模拟节点执行
        await new Promise(resolve => setTimeout(resolve, 1000));

        // 设置当前节点为完成
        myDiagram.model.commit(m => {
            m.set(node, 'status', 'success');
        });

        // 获取下一个节点
        const nextNodes = myDiagram.model.linkDataArray
            .filter(l => l.from === node.key)
            .map(l => nodes.find(n => n.key === l.to));

        // 递归执行后续节点
        for (const nextNode of nextNodes) {
            if (nextNode) {
                await executeNode(nextNode);
            }
        }
    }
}

function commonPostXhr(url, value) {
    let xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(value));
    xhr.onload = function () {
        if (xhr.status === 200) {
            console.log(xhr.responseText);
            window.location.reload();
        } else {
            console.log('Error: ' + xhr.status);
        }
    };
}